/*
 * RDT 3.0 Receiver
 */
#include "np.h"
#include "packet.h"

extern int DEBUG_ON;    // print debug messages if set
static char *port = "5678";        // default port number

//Packet buffers
static Packet     sndPacket, rcvPacket;

// Performance Measure
static int nPackets, nCorrectPackets, nCorruptPackets, nDupPackets;
static int nCorrectBytes;
static struct timeval startTime, endTime;        // gettimeofday()

void printStatistics()
{
    struct timeval interval;
    float duration;         // in sec
    float throughput;        // in bytes per sec

    fprintf(stderr, "Total packets received: %d\n", nPackets);
    fprintf(stderr, "\tdulplicate packets: %d\n", nDupPackets);
    fprintf(stderr, "\tcorrupted packets: %d\n", nCorruptPackets);
    fprintf(stderr, "\tcorrect packets:%d\n", nCorrectPackets);
    timersub(&endTime, &startTime, &interval);
    duration = ((float)interval.tv_sec) + interval.tv_usec/1000000.;
    throughput = nCorrectBytes / duration;
    fprintf(stderr, "Total bytes correctly transfered: %d\n",
            nCorrectBytes);
    fprintf(stderr, "Total time elapsed: %f sec\n", duration);
    fprintf(stderr, "Throughput: %f Bytes/sec\n", throughput);
    return;
}

void deliver_data( char *data, int datalen)
{
    *(data+datalen) = '\0';
    fputs(data, stdout);
    return;
}

int main( int argc, char *argv[] )
{
    int s;
    struct sockaddr_in local, sender;
    unsigned char rcvSeq;
    Packet    *sndpkt = &sndPacket;
    Packet *rcvpkt = &rcvPacket;
    char    *data;
    socklen_t        senderlen;
    static    int        once = 1;
    int opt;

    while ( (opt = getopt(argc, argv, "d")) != -1) {
        switch (opt) {
        case 'd':
            DEBUG_ON = 1;     // Debug mode
            break;
        default:
            error(1, 0, "Usage: %s [-d] [port]\n");
        }
    }
    if (optind == argc-1) {
                port = argv[optind];
    }
    debug("Port = %s\tDebugMode = %s\n", port, DEBUG_ON ? "on" : "off");

    set_address( NULL, port, &local, "udp" );    // Set my all addresses
    s = socket( AF_INET, SOCK_DGRAM, 0 );
    if (  s < 0 )
        error( 1, errno, "socket call failed\n" );

    if ( bind( s, ( struct sockaddr * ) &local, sizeof( local ) ) < 0 )
        error( 1, errno, "bind failed\n" );

    data = &(rcvPacket.data[0]);
    rcvSeq = 0;
    sndpkt = make_pkt(ACK, rcvSeq, &sndPacket, 0);

    for (;;)
    {
        int     datalen, rcvlen;

        senderlen = sizeof(sender);
        rcvlen = recvfrom(s, rcvpkt, sizeof(Packet), 0,
                (struct sockaddr *) &sender,  (socklen_t *) &senderlen);
        if ( rcvlen < 0)
            error (1, errno, "recvfrom failed\n");
        if ( once ) {
            once = 0;
            if ( gettimeofday( &startTime, NULL ) < 0 )    // start of RDT session
                error( 1, errno, "gettimeofday failure" );
        }
        print_pkt("<<", rcvpkt);
        nPackets++;

        // Corrupted packet
        if ( isCorrupt(rcvpkt) ) {
            nCorruptPackets++;
            debug("Corrupted packet received\n");
            udt_send(s, sndpkt, (struct sockaddr *)&sender, senderlen);  // Retx ACK
            continue;
        }
        // Not corrupted packet
        if ( isType(rcvpkt, DATA, rcvSeq) ) { // DATA packet
            // Deliver data to upper
            datalen = rcvlen - sizeof(Header);
            deliver_data(data, datalen);
            debug("DATA packet received. Deliver data to upper\n");
            // Reply ACK
            sndpkt = make_pkt(ACK, rcvSeq, &sndPacket, 0);
            udt_send(s, sndpkt, (struct sockaddr *)&sender, senderlen);
            rcvSeq = (rcvSeq + 1) % 2;
            // Accumulate statistcis
            nCorrectPackets++;
            nCorrectBytes += datalen;
            continue;;
        }
        // END packet
        if ( isType(rcvpkt, END, rcvSeq) ) {
            // Reply ACK
            debug("END packet received.\n");
            sndpkt = make_pkt(END, rcvSeq, &sndPacket, 0);
            // To ensure ACK packet delivery
            udt_send(s, sndpkt, (struct sockaddr *)&sender, senderlen);
            udt_send(s, sndpkt, (struct sockaddr *)&sender, senderlen);
            udt_send(s, sndpkt, (struct sockaddr *)&sender, senderlen);
            if ( gettimeofday( &endTime, NULL ) < 0 )    // End of RDT session
                error( 1, errno, "gettimeofday failure" );
            nPackets--;        // Do not count this END packet
            printStatistics();
            exit(0);
        } else {
        // Duplicated packet
            nDupPackets++;
            debug("Duplicated packet received\n");
            udt_send(s, sndpkt, (struct sockaddr *)&sender, senderlen);  // Retx ACK
            continue;
        }
    } // End of for loop

}
